package base

import (
	"fmt"
	"jvm/nzpJvm/runtime_area"
	"jvm/nzpJvm/runtime_area/heap"
)

// 调用方法。其实就只是为要调用的方法新建了一个栈帧，并且入栈
func InvokeMethod(invokerFrame *runtime_area.Frame, method *heap.Method) {
	// 方法调用者的线程
	thread := invokerFrame.Thread()
	// 新建一个栈帧
	frame := thread.NewFrame(method)
	// 入栈
	thread.PushFrame(frame)

	// 方法的参数数量
	argsSlotSlot := int(method.ArgsSlotCount())
	if argsSlotSlot > 0 {
		// 将所需的所有参数放到新方法栈帧的本地变量表中
		// 这里遍历的时候用i--从后往前遍历，因为第一个参数是最先入栈的，在栈的最下面
		for i := argsSlotSlot - 1; i >= 0; i-- {
			slot := invokerFrame.OperandStack().PopSlot()
			// 操作的直接是slot，所以不需要对long和double进行额外的处理
			frame.LocalVars().SetSlot(uint(i), slot)
		}
	}

	if method.IsNative() {
		// todo 暂时还没完全实现本地方法调用，先直接把栈帧弹出
		fmt.Println("调用" + method.Class().Name() + "类本地方法" + method.Name())
		thread.PopFrame()
	}
}

// 要在currentClass类中，使用object调用它的method方法
func CheckMethodAccess(method *heap.Method, object *heap.Object, currentClass *heap.Class) bool {

	// object对象不是方法所属的类的实例，意味着object对象上不存在目标方法，不能访问
	if !object.IsInstanceOf(method.Class()) {
		return false
	}
	// 到这一步，可以确定object上存在指定的方法
	// public方法，允许任何地方访问，或者方法所属的类就是要调用这个方法的类，那也允许访问
	if method.IsPublic() || method.Class() == currentClass {
		return true
	}

	// protected方法只能被相同包，或者不同包下的子包访问
	if method.IsProtected() {
		// 受保护的方法可以在同一个包下调用
		if method.Class().GetPackageName() == currentClass.GetPackageName() {
			return true
		}
		// 不是同一个包，也允许子类调用
		if currentClass.IsSubClassOf(method.Class()) {
			return true
		}
		// protected方法，其他情况，不允许调用
		return false
	}
	if method.IsPrivate() {
		// 私有方法只能在同一个类内部调用
		return false
	}
	// 默认的访问权限，只能在同一个包下访问
	return method.Class().GetPackageName() == currentClass.GetPackageName()
}
